#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define FILENAME "log.txt"

// int main() {

//   /*
//     文件描述符的分配规则:
//       当打开一个文件时
//       新文件的文件描述符fd
//       默认为最小的且未被占用的文件描述符表下标
//   */
//   // close(0);
//   // close(1);
//   close(2);
//   int fd = open(FILENAME, O_WRONLY | O_TRUNC | O_CREAT, 0666);
//   if (fd < 0) {
//     perror("open fail\n");
//     return -1;
//   }

//   printf("fd = %d\n", fd);

//   const char* massage = "hello world\n";
//   int cnt = 5;
//   while (cnt--) {
//     write(fd, massage, strlen(massage));
//   }
//   close(fd);

//   return 0;
// }

//---------------------

// int main() {
//   close(1);

//   /*
//     当将文件描述符1(对应的向显示器写入的文件描述符)关闭时
//     对应的新的文件的文件描述符将为1
//     在下面的写入操作时仍然会向文件描述符1进行写入
//     但此时的文件描述符1对应的并不是显示器文件而是新打开的文件
//     ###########################################
//      ######### 这就是输出重定向的本质 #########
//     ###########################################
//   */

//   int fd = open(FILENAME, O_WRONLY | O_TRUNC | O_CREAT, 0666);
//   if (fd < 0) {
//     perror("open fail\n");
//     return -1;
//   }

//   const char* massage = "hello world\n";
//   int cnt = 5;
//   while (cnt--) {
//     write(1, massage, strlen(massage));
//     // 将写入的文件描述符改为1
//   }
//   close(fd);

//   return 0;

//   // 但是对于该种方法而言 该种方法过于冗余
//   // 需要自行关闭文件描述符
//   // 而在Linux操作系统当中 存在着对应的接口
//   /*
//     dup dup2 dup3
//     其中dup2最为常用
//   */
// }

// //---------------------

// int main() {
//   /*
//       dup2()  makes  newfd  be  the  copy of oldfd,
//       closing newfdfirst if necessary,
//       but note the following:
//       dup2() 将会关闭newfd文件描述符并将oldfd文件描述符拷贝一份
//       并覆盖newfd
//       //这里的拷贝需要注意 拷贝的不是文件描述符(文件描述符表中对应的下标)
//       //而是文件描述符表中每个下标对应所存的指针
//       //意思是当使用dup2后将会有同时两个变量存储同一个指针
//       //当然若是想预防文件描述符的过度占用
//       可以在使用过后将原有的文件描述符关闭

//        *  If oldfd is not a valid file descriptor, then  the  call
//           fails, and newfd is not closed.
//         如果所传入的文件描述符为非有效文件描述符则调用失败
//         且newfd文件描述符不关闭

//        *  If  oldfd  is a valid file descriptor, and newfd has the
//           same value as  oldfd,  then  dup2()  does  nothing,  and
//           returns newfd.
//         如果oldfd与newfd相同 则dup2不作任何操作 并返回newfd
//   */
//   int fd = open(FILENAME, O_WRONLY | O_TRUNC | O_CREAT, 0666);
//   if (fd < 0) {
//     perror("open fail\n");
//     return -1;
//   }
//   int ret = dup2(fd,1);
//   /*
//     int dup2(int oldfd, int newfd);
//     利用oldfd覆盖newfd
//       -- 狸猫换太子 --
//     RETURN VALUE
//        On  success,  these system calls return the new descriptor.
//        On error, -1 is returned, and errno is set appropriately.
//     如果dup2调用成功则返回新的文件描述符(oldfd)
//     如果调用失败则返回-1 并设置error
//   */
//   fprintf(stderr, "fd = %d\n", fd);
//   if (ret < 0) {
//     perror("dup2\n");
//   }
//   const char* massage = "hello world\n";
//   int cnt = 5;
//   while (cnt--) {
//     write(1, massage, strlen(massage));
//     //返回值为ssize_t 此处不考虑函数调用失败
//     //注意这里是向文件描述符1进行写入
//   }
//   close(fd);
//   //使用过后可以将多余的文件描述符关闭,即使不关也不会有过多影响
//   /*
//     而输出重定向与追加重定向的区别也只有文件的打开方式不同仅此而已
//     (一个打开时将会进行清空O_TRUNC 一个打开时将会进行追加O_APPEND)
//   */
//   return 0;
// }

//---------------------

// int main() {
//   // 该条件下 文件已经存在(log.txt) 且文件内存在内容
//   int fd = open(FILENAME, O_RDONLY);
//   // 将文件以只读方式打开

//   char buf[1024];
//   //创建缓冲区 存放read接口

//   if (fd < 0) {
//     perror("open fail\n");
//     return -1;
//   }
//   ssize_t rdret = read(0, buf, sizeof(buf) - 1);
//   /*
//     此处使用的文件描述符为0
//     故在执行程序的时候 进程将自动阻塞
//     并等待用户从键盘中获取数据
//   */
//   if(rdret<0){
//    perror("read\n");
//    return -1;
//  }
//   buf[rdret] = '\0';
//   close(fd);
//   printf("%s", buf);
//   /*
//     当用户从键盘中获取输入数据的时候
//     将从键盘中获取的数据利用printf打印至屏幕中
//   */
//   return 0;
// }

// // //---------------------

// int main() {

//   //该条件下 文件已经存在(log.txt) 且文件内存在内容
//   int fd = open(FILENAME, O_RDONLY);
//   //将文件以只读方式打开

//   char buf[1024];
//   //创建缓冲区 以获取read的结果

//   if (fd < 0) {
//     //避免open调用失败
//     perror("open fail\n");
//     return -1;
//   }
//   int dupret = dup2(fd, 0);
//   // 新打开的文件的文件描述符
//   // 当中对应的指针内容将覆盖文件描述符0中的指针内容
//   if (dupret < 0) {
//     //避免dup2调用失败
//     perror("dup2\n");
//   }
//   ssize_t rdret = read(0,buf,sizeof(buf)-1);
//   //这里在sizeof(buf)后进行-1是一种防御性编程
//   //确保在后面的buf[rdret]中能确保
//   //无论读取的数据长度有多长
//   //起码在字符串的最后能够有一个'\0'
//   if(rdret<0){
//     perror("read fail\n");
//     return -1;
//   }
//   buf[rdret] = '\0';
//   close(fd);
//   printf("%s", buf);
//   return 0;
//   /*
//     在该段代码当中 使用了dup2关闭了0号文件描述符
//     并且将新打开的文件的文件描述符表下标内对应的指针内容
//     拷贝到0号文件描述符当中

//     但在以读取的方式还是读取0号文件描述符中的内容
//     即使如此 文件描述符0中原有的键盘设备已经被关闭
//     对应的0号文件描述符对应的文件为新打开的文件
//     故实际上在这段代码当中是从新打开的文件即log.txt文件当中读取数据
//     并放置缓冲区当中

//     该端操作实际上即为输入重定向  */
// }

// //////////////////////////////

// int main()
// {
//   int fd = open(FILENAME, O_WRONLY | O_APPEND | O_APPEND, 0666);

//   if(fd<0){
//     perror("open fail\n");
//   }

//   printf("fd: %d\n", fd);
//   printf("hello printf\n");
//   fprintf(stdout,"hello printf\n");
//   /*
//     在这段代码当中 以追加的方式打开文件
//     并进行普通输出 最终结果将打印到显示器当中
//   */
//   return 0;
// }

//----------------

// TODO

// int main() {
//   int fd = open(FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);

//   if (fd < 0) {
//     perror("open fail\n");
//   }
//   int dret = dup2(fd, 1);
//   if (dret < 0) {
//     perror("dup2 fail\n");
//   }

//   printf("fd: %d\n", fd);
//   printf("hello printf\n");
//   fprintf(stdout, "hello printf\n");

//   /*
//     追加重定向
//   */
//   return 0;
// }

////// 完成shell 的重定向接口 //////

/*
    思考一个问题 在shell当中已经完成了重定向接口的实现
    那为什么进程替换execl接口不会影响已经完成的dup2重定向操作?

    该处对应在微信传输助手中存在一张截图

    进程本身是在磁盘当中的一个exe文件
    当运行后OS将会为了维护这个进程而创建对应的PCB结构
    对应存在的有虚拟地址 页表 以及物理内存地址

    而在test_struct当中存在一个struct file_struct*指针
    指向了file_struct结构体变量
    在这个结构体变量当中有一个file*的数组被称作为文件描述符表
    表中的每个指针指向一个struct file结构体从而进行管理

    而这本身是两个体系
    即task_struct , fies_struct , struct file 为内核数据结构
    构成文件管理
    而mm_struct 页表 物理内存
    构成内存管理

    且在之前的博客当中提到 对于进程替换而言 并不是新建一个进程
    当发生进程替换后 进程中的代码和数据将被替换
    但由于不会新建一个进程
    而进程本身的文件系统中的文件描述符等等设置已经设置完毕
    进程替换后的新的代码和数据将会延用该进程的文件描述符设置
    故当发生进程替换后并不会影响对应的文件描述符设置 例如dup操作等等

    而这成功的使得Linux在文件管理与内存管理进行了解耦合

*/

////////////////////////////////////

// int main() {
//   fprintf(stdout, "normal stdout \n");
//   fprintf(stdout, "normal stdout \n");
//   fprintf(stdout, "normal stdout \n");
//   fprintf(stdout, "normal stdout \n");
//   fprintf(stdout, "normal stdout \n");

//   fprintf(stderr, "error stderr \n");
//   fprintf(stderr, "error stderr \n");
//   fprintf(stderr, "error stderr \n");
//   fprintf(stderr, "error stderr \n");
//   fprintf(stderr, "error stderr \n");
//   /*
//     当直接运行该程序时将直接进行打印 没有任何疑问
//     而当将程序以普通的输入重定向重定向到 normal.txt 文件后:

//   $ ./mytest > normal.txt

//     "error stderr" 信息将直接打印到显示器当中
//     而normal.txt文件当中只会存在 "normal stdout"信息

//     这个原因实际上是由于虽然都是打印到显示器当中
//     但是一个是利用stdout文件流进行打印
//     而另一个是利用stderr文件流进行打印
//     两个文件流不同 对应的文件描述符也不同

//     而 " > " 为输出重定向
//     输出重定向对应的是将标准输出重定向到 对应的文件(normal.txt)当中
//     而不是将标准错误重定向到文件当中

//     故使用stderr文件流打印到显示器当中再使用输出重定向时
//     stderr文件流所打印出来的消息不会被重定向到文件当中而是继续打印到桌面当中

//   */

//   /*

//     而使用:
//       $ ./mytest 2> error.txt
//       对应的"normal tsdout"信息将会被打印到屏幕当中
//       而对应的error.txt文件当中只会存在"error stderr"信息

//       原因是实际上 '2>' 与 '2>>' 也是一种重定向
//       若是将 ">" ,">>"称为标准输出重定向的话
//       那么对应的"2>" 与 "2>>" 即为标准错误重定向
//       即将标准错误文件描述符2重定向到文件当中

//       故在使用fprintf时以stderr为文件流进行输出并使用标准错误重定向时
//       由于标准错误被重定向到对应的文件中

//       故文件当中只会存在 标准错误文件流所打印的信息
//       而使用标准输出文件流所打印的信息将被打印到显示器上
//       (未重定向标准输出文件描述符)
//   */

//   /*
//     而实际上对于" > "," >> "与' 2> '," 2>> "情况的类似
//     实际上" > "," >> "就是" 1> "," 1>> " 即为标准输出重定向;
//     只不过其中的'1'可以省略
//     即">",">>"默认就是为" 1> "," 1>> " 标准输出重定向

//    除了上面的用法 还可以直接使用:
//    "./mytest 2> error.txt > normal.txt" 或是
//     "./mytest > normal.txt 2> error.txt";
//   都是使用一条指令 将不同的文件描述符重定向到不同的文件当中
//   即 文件描述符2重定向到error.txt
//      文件描述符1重定向到normal.txt文件当中
//   */

//   /*
//      而若是希望将标准输出所打印的信息与
//      标准错误打印的信息同时存放在同一个文件
//      以上述为例子即为:
//      "./mytest 1> all.txt 2>&1"
//      即为 将文件描述符1重定向到all.txt文件
//      而将文件描述符2重定向到文件描述符1
//      这里所说的将文件描述符2重定向到文件描述符1
//      的说法并不是单纯的将指针进行重定向到文件描述符1的部分
//      因为文件描述符1本身就不是文件
//      而是将文件描述符2重定向到文件描述符1被重定向的文件当中

//      其中在上文中提到 对于标准输出重定向 "1>" 可以省略为 ">"
//      故可以简写为 "./mytest > all.txt 2>&1"

//      类似 " fd1 >& fd2 " 这样的写法还有很多;
//      不过对于文件描述符 0 1 2 来说 是默认打开的
//      而若是需要使用其他文件描述符则需要确保文件描述符是被某个文件所占用
//      且确保这个文件被打开

//   */
//   return 0;
// }

////////////////////////////


/*如何理解 "Linux下 一切皆文件"*/

/*
    以硬件设备为例 硬件设备创始的初中是为用户与操作系统提供服务的 故在设计硬件等设备时
    必然会为用户与操作系统提供对应的接口供使用
    这里的硬件举例为 键盘 显示器 磁盘 网卡 等等 
    不包括内存与CPU(内存与CPU一般为OS直接调用)

    不仅提供了接口 对应的硬件设备也需要提供对应的识别码(用于唯一标识一种硬件设备)

    而在Linux当中 存在着一种硬件识别机制
    这里主要是设计两个重要的组件: 设备驱动程序以及udev系统

    这个硬件识别机制一般涉及到读取设备的识别码 
    而这些识别码就如上文当中提到的为硬件制造商所分配
    当操作系统识别到对应的设备识别码后将会在OS中已有的驱动程序中进行匹配调用(加载)适用的驱动程序
    而若是这个硬件对应的驱动程序不存在时则对应的硬件设备可能无法正常工作
    (一般情况下设备的驱动程序在Linux 当中都是有的 但是如果不具备该驱动程序时用户应该进行手动安装)

    而在Linux当中在 /dev 目录当中存在着对应的设备文件
    这些设备文件主要是为用户空间的应用程序提供了访问硬件设备的途径

    具体的流程可以如下:
    用户空间程序 -> 设备文件 -> 驱动程序 -> 硬件设备

    用户空间程序: 用户控件是操作系统外的一个层级 一般是普通应用程序执行的地方
    设备文件: 在/dev目录当中的特殊文件 代表系统中的硬件设备 这些文件为用户空间的程序
            提供了一个接口 用户可以通过这些接口程序给驱动程序发送请求 从而间接的调用硬件

    驱动程序: 驱动程序是系统内核的一部分 是一种较为特殊的软件 负责管理和控制特定的硬件设备
    当用户空间的程序通过设备文件发起请求时 相应的驱动程序会接收到这个请求并执行必要的操作
    以硬件设备进行交互


    硬件设备:实际的物理设备
              
    需要注意 驱动程序管理和控制的硬件 而本身驱动程序是被OS进行管理的
    设备文件本身是为用户提供一个用户与驱动程序交互的一个接口
    而驱动程序则为 为设备文件提供的一个与硬件交互的一个接口
    故在Linux当中一句哲学思想为 "Linux当中一切皆为文件"
    这些抽象的接口本身对于OS是不知情的 他只知道在调用对应的硬件时只要去调用对应的设备文件即可

    在Linux当中 每个设备文件
    对于硬件层来说 硬件层本身并不提供读写方法 而是通过电子接口与通信协议与外界进行交互的
    对于驱动程序层而言 驱动程序作为硬件与OS操作的桥梁 实现了对硬件进行读写的方法 
    并为上层提供了对应的方法
    设备文件层而言 设备文件为用户提供了一个标准的接口 当用户空间程序执行了对应的读写操作时
    实际上通过设备文件与驱动程序通信 驱动程序随后与硬件本身进行交互
    对于用户控件层 用户空间程序将使用标准的系统调用 例如open read write等接口方法与设备文件
    进行交互 操作系统将接受到这些调用 并通过驱动程序将他们转化为对硬件设备的操作

    而在上文中提到 设备文件是检测到硬件设备后动态创建的 而在Linux当中 当检测到设备并为其创建设备文件时
    这些设备文件往往是具有同样的读写接口从而方便设备文件与驱动程序之间的交互 
    而正因为是动态创建的 故这些接口往往是统一的 当用户层空间程序通过设备文件对硬件进行操作时
    对应的将经过驱动程序 而驱动程序将根据设备的特性进行条件编译 当遇到不适合的操作时将为上层(设备文件)返回错误
    而当设备文件接收到错误时 可能将继续返回上层并告诉其该操作并不支持 记录到错误日志 或是不做任何响应

    这也完成了一个(硬件设备 驱动程序) (用户 操作系统) (操作系统 设备文件)的解耦合

    同时还需要注意几点:
    设备文件是当OS检测到对应的硬件时动态创建的

    且硬件当中也将会出现不兼容的问题 不兼容的问题原因有多重 一方面可能是硬件为提供标准的识别码
    使得OS无法识别设备
    同时 即使设备被识别 如果没有合适的驱动程序来实现与硬件的通信协议与接口 该设备也无法在OS中被调用







*/



/*
    虚拟文件系统(VFS)

    当需要去调用一个硬件的时候
    用户层将转至内核层
    (当前进程)
    而对应的OS将会去查看当前进程对应的task_stuct并获取其的进程详细信息
    当获取完对应的进程详细信息后 对应的操作系统将会根据该进程的请求打开设备文件
    当打开对应的设备文件后 进程的task_struct中的files_struct*指针指向的files_struct对象中的
    fd_array[]数组将为这个新打开的设备文件维护一个新的文件描述符
    对应的文件描述符(下标)中对应的数组元素的file结构体将存储该设备文件的信息
    对应的file结构体当中还会包含一个指向对应设备驱动程序的函数指针
    对应的这些函数指针构成了一个文件操作表[file_operations]的合计
    其中包含了具体的设备驱动程序提供的操作接口

    OS将通过这个函数指针去调用设备驱动程序中对应的操作方法
    从而使得进程间接的调用硬件设备
*/

int main() {
  //
  return 0;
}

////////////////////////////
////////////////////////////
////////////////////////////
/*
    244
    010100100

    101011011
    110110110

    100 010 010
     4   2   2  

*/
